/*
 * Copyright (c) 2013 Michael Armbruster
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
 * of the Software, and to permit persons to whom the Software is furnished to do
 * so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;

namespace MangafoxPDFGenerator
{
	/// <summary>
	/// Provides methods for various purposes like getting Assembly properties
	/// or performing file or string manipulations, etc.
	/// </summary>
	public static class API
	{
		/// <summary>
		/// The regular expression to find the volume number (i.e. the book
		/// number) when using the HakuNeko naming convention.
		/// </summary>
		private static readonly Regex VOLUME_REGEX_HAKUNEKO =
			new Regex (@"\[Volume (.*?)\]");

		/// <summary>
		/// The regular expression to find the episode number (i.e. the sequential
		/// number) when using the HakuNeko naming convention.
		/// </summary>
		private static readonly Regex EPISODE_REGEX_HAKUNEKO =
			new Regex (@"\] - (\d{4,}(\.\d)?) - ");

		/// <summary>
		/// The regular expression to find the episode name when using the
		/// HakuNeko naming convention.
		/// </summary>
		private static readonly Regex EPISODE_NAME_REGEX_HAKUNEKO =
			new Regex (@"\] - \d{4,}(\.\d)? - (.*)");

		/// <summary>
		/// The regular expression to identify a valid directory by its name when
		/// using the HakuNeko naming convention.
		/// </summary>
		private static readonly Regex VALID_EPISODE_REGEX_HAKUNEKO =
			new Regex (@"\[Volume (.*?)\] - \d{4,}(\.\d)? - (.*)");

		/// <summary>
		/// Gets all valid manga subdirectories in the given directory.
		/// </summary>
		/// <returns>
		/// A list of all valid manga directories with their complete path.
		/// </returns>
		/// <param name='directory'>
		/// The working directory (base directory).
		/// </param>
		public static List<string> GetValidMangaDirectories (string directory)
		{
			// Get all subdirectories of the given working directory
			string[] subdirectories = Directory.GetDirectories (
				directory,
				"*",
				SearchOption.TopDirectoryOnly
			);

			// Create a list to save all valid subdirectories
			List<string> validDirectories = new List<string>();

			// Iterate through all subdirectories (i.e. their names)
			foreach (string subdir in subdirectories)
			{
				// Get the plain directory name of the subdirectory
				string dirname = Path.GetFileName(subdir);

				// If the regular expression matches the plain directory name, we
				// will save the complete folder path to our list
				if(VALID_EPISODE_REGEX_HAKUNEKO.IsMatch(dirname))
					validDirectories.Add(subdir);
			}

			// Return the list of all valid directories
			return validDirectories;
		}

		/// <summary>
		/// Gets the name of the episode properties by using the HakuNeko folder
		/// naming conventions.
		/// </summary>
		/// <returns>
		/// The episode properties.
		/// </returns>
		/// <param name='hakuNekoFolder'>
		/// A HakuNeko folder name (e.g. "[Volume 2] - 0014 - Name of episode").
		/// </param>
		public static Dictionary<string, string>
			GetPropertiesByHakuNekoFolderName (string hakuNekoFolder)
		{
			// Get the match for each property with our predefined regular
			// expressions
			Match volumeMatch = VOLUME_REGEX_HAKUNEKO.Match (hakuNekoFolder);
			Match episodeMatch = EPISODE_REGEX_HAKUNEKO.Match (hakuNekoFolder);
			Match episodeNameMatch = EPISODE_NAME_REGEX_HAKUNEKO
				.Match (hakuNekoFolder);

			// Create a new dictionary which will hold the properties as key-value
			// pairs
			Dictionary<string, string> properties =
				new Dictionary<string, string> (3);

			// Add each property to the dictionary
			properties.Add ("volume", volumeMatch.Groups [1].Value);
			properties.Add ("episode", episodeMatch.Groups [1].Value);
			properties.Add ("episodeName", episodeNameMatch.Groups [2].Value);

			// Return the dictionary
			return properties;
		}

		/// <summary>
		/// Gets the name of the program which equals the
		/// <see cref="GetAssemblyReadableName" /> method.
		/// </summary>
		/// <returns>
		/// The program name.
		/// </returns>
		public static string GetProgramName ()
		{
			return API.GetAssemblyReadableName ();
		}

		/// <summary>
		/// Gets the readable name of the assembly which is also known as the
		/// program name.
		/// </summary>
		/// <returns>
		/// The readable assembly name.
		/// </returns>
		public static string GetAssemblyReadableName ()
		{
			// Variable to determine if the last letter was uppercase
			bool lastWasUppercase = false;

			// Variable to determine if the prelast letter was uppercase
			bool prelastWasUppercase = false;

			// Get the assembly name
			string name = API.GetAssemblyName ();

			// StringBuilder to build the readable name
			StringBuilder builder = new StringBuilder ();

			// Iterate through all characters
			foreach (char c in name) {
				// First of all, we will save, if the prelast character was
				// uppercase (after this iteration, the now last character will
				// be the prelast, so we need a temp variable here to save this)
				bool tempPrelastWasUppercase = lastWasUppercase;

				// We need to treat the characters another way if the last one
				// was uppercase
				if (lastWasUppercase) {
					// If this character is not uppercase, and the last one was
					// uppercase, and the prelast was uppercase too, we started a
					// new word with the last uppercase character, so we need to
					// insert a space between the prelast and the last character
					if (prelastWasUppercase && char.IsLower (c))
						builder.Insert (builder.Length - 1, " ");

					// Since the last character was uppercase and we already
					// checked if we started a new word, we won't need under any
					// circumstances to add a space between the last and this
					// character, so we can append it now
					builder.Append (c);

					// Now we only need to save if this character is uppercase to
					// recognize it in the next iteration
					lastWasUppercase = char.IsUpper (c);
				} else {
					// Is this character uppercase?
					if (char.IsUpper (c)) {
						// If yes, we need to append a space before our character
						builder.Append (" " + c);

						// Save to the variable that our last character was an
						// uppercase character
						lastWasUppercase = true;
					} else {
						// If no, we will only append our character
						builder.Append (c);

						// Make sure, we will recognize that the last character
						// was not uppercase (although this should be set already)
						lastWasUppercase = false;
					}
				}

				// Save the prelast character to the recognizable variable for the
				// next iteration
				prelastWasUppercase = tempPrelastWasUppercase;
			}

			// Return the content of our StringBuilder object which contains now
			// the readable assembly name (also known as program name)
			// [WORKAROUND: We trim the string to avoid the prepended space
			// character. Whoever wants to find the mistake in the code why this
			// character is prepended, feel free to do so, but trimming the string
			// before returning it is never a bad idea in my opinion; Michael
			// Armbruster]
			return builder.ToString ().Trim ();
		}

		/// <summary>
		/// Gets the name of the assembly.
		/// </summary>
		/// <returns>
		/// The assembly name.
		/// </returns>
		public static string GetAssemblyName ()
		{
			return Assembly.GetCallingAssembly ().GetName ().Name;
		}

		/// <summary>
		/// Gets the full name of the assembly.
		/// </summary>
		/// <returns>
		/// The assembly full name.
		/// </returns>
		public static string GetAssemblyFullName ()
		{
			return Assembly.GetCallingAssembly ().GetName ().FullName;
		}

		/// <summary>
		/// Gets the assembly version.
		/// </summary>
		/// <returns>
		/// The assembly version.
		/// </returns>
		public static string GetAssemblyVersion ()
		{
			return Assembly.GetCallingAssembly ().GetName ().Version.ToString ();
		}

		/// <summary>
		/// Gets the assembly copyright notice.
		/// </summary>
		/// <returns>
		/// The copyright notice.
		/// </returns>
		public static string GetAssemblyCopyrightNotice ()
		{
			// Get all Copyright attributes on this assembly
			object[] attributes = Assembly.GetCallingAssembly ()
				.GetCustomAttributes (typeof(AssemblyCopyrightAttribute), false);

			// If there aren't any Copyright attributes, return an empty string
			if (attributes.Length == 0)
				return "";

			// If there is a Copyright attribute, return its value
			return ((AssemblyCopyrightAttribute)attributes [0]).Copyright;
		}

		/// <summary>
		/// Calls the ImageMagick 'convert' command.
		/// </summary>
		/// <returns>
		/// <c>true</c>, if the ImageMagick command was successful, <c>false</c>
		/// otherwise.
		/// </returns>
		/// <param name='image'>
		/// The input image to use for the 'convert' command.
		/// </param>
		/// <param name='inputArguments'>
		/// The arguments to use for the input image.
		/// </param>
		/// <param name='image2'>
		/// The output image of the 'convert' command-
		/// </param>
		/// <param name='outputArguments'>
		/// The arguments to use for the output image.
		/// </param>
		public static bool CallIMConvert (
			string image,
			string inputArguments,
			string image2,
			string outputArguments)
		{
			// Create new process infos and set the command to the convert command
			ProcessStartInfo pInfo = new ProcessStartInfo("convert.exe");

			// Set the given arguments in the right order
			pInfo.Arguments = inputArguments + " \"" + image + "\" " +
				outputArguments + " \"" + image2 + "\"";

			// Suppress the VISIBLE creation of a new window
			pInfo.WindowStyle = ProcessWindowStyle.Hidden;

			// Start the process and wait until it finished
			Process p = Process.Start(pInfo);
			p.WaitForExit();

			// Return if the command was successfully executed
			return (p.ExitCode == 0);
		}

		/// <summary>
		/// Converts all jpg images in one folder to one PDF.
		/// </summary>
		/// <returns>
		/// <c>true</c>, if the PDF was converted successfully, <c>false</c>
		/// otherwise.
		/// </returns>
		/// <param name='subdir'>
		/// The directory which contains the input files (only jpgs are possible).
		/// </param>
		/// <param name='output'>
		/// The output filename for the PDF (can include a path).
		/// </param>
		/// <param name='quality'>
		/// The quality for the output file (must be between 1 and 100). A value
		/// above 90 is recommended.
		/// </param>
		public static bool ConvertToPDF (string subdir, string output, int quality)
		{
			// Check if the quality is in the valid range 1-100 and change it
			// if the given quality is invalid
			if(quality < 1)
				quality = 1;
			else if(quality > 100)
				quality = 100;

			// Create new process infos and set the command to the convert command
			ProcessStartInfo pInfo = new ProcessStartInfo("convert.exe");
			
			// Set the given arguments in the right order
			pInfo.Arguments = "\"" + Path.GetFullPath(subdir) +
				Path.DirectorySeparatorChar + "*.jpg\" -quality " + quality +
				" \"" +	output + "\"";
			
			// Suppress the VISIBLE creation of a new window
			pInfo.WindowStyle = ProcessWindowStyle.Hidden;
			
			// Start the process and wait until it finished
			Process p = Process.Start(pInfo);
			p.WaitForExit();
			
			// Return if the command was successfully executed
			return (p.ExitCode == 0);
		}
	}
}

